%%%-------------------------------------------------------------------
%%% @author Anil Kumar S. R. <sranil@gmail.com>
%%% @copyright (C) 2010, 
%%% @doc
%%%
%%% @end
%%% Created : 10 Oct 2010 by Anil Kumar S. R. <sranil@gmail.com>
%%%-------------------------------------------------------------------
-module(uac_core).
-export([packet/1]).

-include("../include/sip.hrl").
-include("../include/b2bua.hrl").

packet([register, noauth, Expires_Time, SIP_Session_State]) ->
    #sip_session_state{userid = Userid, mapped_address = Mapped_Address, cseq = CSeq_Num, transaction_table = Transaction_Table, dialog_table = Dialog_Table} = SIP_Session_State,

    Dialog_List = ets:match_object(Dialog_Table, #dialog{method = "REGISTER", _='_'}),
    if
        Dialog_List /= [] ->
            Dialog_State = hd(Dialog_List);
        true ->
            Dialog_State = ""
    end,
            
    Method = "REGISTER",

    Request_Line = request_line(Method, "sip:" ++ ?SIP_SERVER),
    Branch_ID = "z9hG4bK" ++ utils:generate_random_string(),
    Via = via(Mapped_Address, Branch_ID),

    if
        Dialog_State == "" ->
            From_URI = ["<sip:", Userid, $@, ?SIP_SERVER, ">"],
            From_Tag = utils:generate_random_number(9999999999),
            From = from(From_URI, From_Tag);
        true ->
            #dialog{local_uri = From_URI, local_tag = From_Tag} = Dialog_State,
            From = from(From_URI, From_Tag)
    end,

    if
        Dialog_State == "" ->
            To_URI = ["<sip:", Userid, $@, ?SIP_SERVER, ">"],
            To = to(To_URI);
        true ->
            #dialog{remote_uri = To_URI, remote_tag = To_Tag} = Dialog_State,
            To = to(To_URI, To_Tag)
    end,
    
    CSeq = cseq(CSeq_Num, Method),

    if
        Dialog_State == "" ->
            Call_ID_Str = utils:generate_random_string(),
            Call_ID = callid(Call_ID_Str);
        true ->
            #dialog{call_id = Call_ID_Str} = Dialog_State,
            Call_ID = callid(Call_ID_Str)
    end,

    Max_Forwards = max_forwards(),
    
    Contact = contact(Userid, Mapped_Address),

    User_Agent = user_agent(),

    Expires = expires(Expires_Time),
    
    Content_Length = content_length(0),
    
    Request = [Request_Line, Via, From, To, CSeq, Call_ID, Max_Forwards, Contact, User_Agent, Expires, Content_Length, "\r\n"],
    Transaction_Record = #transaction_record{expires = Expires_Time, auth_count = 0},
    ets:insert(Transaction_Table, #transaction{branch_id = Branch_ID, method = "REGISTER", cseq = CSeq_Num, call_id = Call_ID_Str, transaction_record = Transaction_Record}),
    {Branch_ID, Request, SIP_Session_State#sip_session_state{cseq = CSeq_Num + 1}};

packet([register, auth, Expires_Time, Digest, Auth_Count, Realm, Nonce, Nonce_Count, Client_Nonce, QOP, SIP_Session_State]) ->
    #sip_session_state{userid = Userid, mapped_address = Mapped_Address, cseq = CSeq_Num, transaction_table = Transaction_Table, dialog_table = Dialog_Table} = SIP_Session_State,

    Dialog_List = ets:match_object(Dialog_Table, #dialog{method = "REGISTER", _='_'}),
    if
        Dialog_List /= [] ->
            Dialog_State = hd(Dialog_List);
        true ->
            Dialog_State = ""
    end,
            
    Method = "REGISTER",

    Request_Line = request_line(Method, "sip:" ++ ?SIP_SERVER),
    Branch_ID = "z9hG4bK" ++ utils:generate_random_string(),
    Via = via(Mapped_Address, Branch_ID),

    if
        Dialog_State == "" ->
            From_URI = ["<sip:", Userid, $@, ?SIP_SERVER, ">"],
            From_Tag = utils:generate_random_number(9999999999),
            From = from(From_URI, From_Tag);
        true ->
            #dialog{local_uri = From_URI, local_tag = From_Tag} = Dialog_State,
            From = from(From_URI, From_Tag)
    end,

    if
        Dialog_State == "" ->
            To_URI = ["<sip:", Userid, $@, ?SIP_SERVER, ">"],
            To = to(To_URI);
        true ->
            #dialog{remote_uri = To_URI, remote_tag = To_Tag} = Dialog_State,
            To = to(To_URI, To_Tag)
    end,
    
    CSeq = cseq(CSeq_Num, Method),

    if
        Dialog_State == "" ->
            Call_ID_Str = utils:generate_random_string(),
            Call_ID = callid(Call_ID_Str);
        true ->
            #dialog{call_id = Call_ID_Str} = Dialog_State,
            Call_ID = callid(Call_ID_Str)
    end,

    Max_Forwards = max_forwards(),
    
    Contact = contact(Userid, Mapped_Address),

    User_Agent = user_agent(),

    Expires = expires(Expires_Time),

    Authorization = authorization(Method, Userid, Realm, Nonce, ?SIP_SERVER, "md5", Digest, Client_Nonce,
                                  lists:foldl(fun(X, Y) -> Y ++ [X+48] end, [], binary_to_list(<<Nonce_Count:8/integer-unit:8>>)),
                                  QOP),

    Content_Length = content_length(0),
    
    Request = [Request_Line, Via, From, To, CSeq, Call_ID, Max_Forwards, Contact, User_Agent, Expires, Authorization, Content_Length, "\r\n"],
    Transaction_Record = #transaction_record{expires = Expires_Time, auth_count = Auth_Count, nonce = Nonce, nonce_count = Nonce_Count},
    ets:insert(Transaction_Table, #transaction{branch_id = Branch_ID, method = "REGISTER", cseq = CSeq_Num, call_id = Call_ID_Str, transaction_record = Transaction_Record}),
    {Branch_ID, Request, SIP_Session_State#sip_session_state{cseq = CSeq_Num + 1}};
                                                             
packet([invite, noauth, To_ID, SIP_Session_PID, SIP_Session_State]) ->
    #sip_session_state{userid = Userid, mapped_address = Mapped_Address, cseq = CSeq_Num, transaction_table = Transaction_Table} = SIP_Session_State,
    
    Method = "INVITE",
    
    Request_Line = request_line(Method, "sip:" ++ To_ID ++ "@" ++ ?SIP_SERVER),
    Branch_ID = "z9hG4bK" ++ utils:generate_random_string(),
    Via = via(Mapped_Address, Branch_ID),

    From_URI = ["<sip:", Userid, $@, ?SIP_SERVER, ">"],
    From_Tag = utils:generate_random_number(9999999999),
    From = from(From_URI, From_Tag),
 
    To_URI = ["<sip:", To_ID, $@, ?SIP_SERVER, ">"],
    To = to(To_URI),
    
    CSeq = cseq(CSeq_Num, Method),

    Call_ID_Str = utils:generate_random_string(),
    Call_ID = callid(Call_ID_Str),

    Max_Forwards = max_forwards(),
    
    Contact = contact(Userid, Mapped_Address),

    User_Agent = user_agent(), 

    Content_Type = content_type("application/sdp"),

    if
        SIP_Session_State#sip_session_state.media_session_pid /= -1 ->
            gen_server:cast(SIP_Session_State#sip_session_state.media_session_pid, kill_process);
        true ->
            nothing
    end,

    case catch media_session:start([SIP_Session_PID]) of
        {ok, PID} ->
            case catch gen_server:call(PID, create_media_socket, infinity) of
                {ok, RTP_Port} ->
                    SDP_Data = generate_sdp([Userid, RTP_Port]),
    
                    Content_Length = content_length(length(SDP_Data)),
                    
                    Request = [Request_Line, Via, From, To, CSeq, Call_ID, Max_Forwards, Contact, User_Agent, Content_Type, Content_Length, "\r\n", SDP_Data],
                    Transaction_Record = #transaction_record{auth_count = 0, to = To_ID},
                    ets:insert(Transaction_Table, #transaction{branch_id = Branch_ID, method = "INVITE", cseq = CSeq_Num, call_id = Call_ID_Str, transaction_record = Transaction_Record}),
                    {Branch_ID, Request, SIP_Session_State#sip_session_state{cseq = CSeq_Num + 1, media_session_pid = PID, ua_state = ?CALL_IN_PROGRESS}};
                {error, Error_Code} ->
                    gen_server:cast(PID, kill_process),
                    {error, Error_Code};
                _ ->
                    gen_server:cast(PID, kill_process),
                    {error, "106"}
            end;
        _ ->
            {error, "106"}
    end;

packet([invite, auth, To_ID, Digest, Auth_Count, Realm, Nonce, Nonce_Count, Client_Nonce, QOP, SIP_Session_PID, SIP_Session_State]) ->
    #sip_session_state{userid = Userid, mapped_address = Mapped_Address, cseq = CSeq_Num, transaction_table = Transaction_Table} = SIP_Session_State,
    
    Method = "INVITE",
    
    Request_Line = request_line(Method, "sip:" ++ To_ID ++ "@" ++ ?SIP_SERVER),
    Branch_ID = "z9hG4bK" ++ utils:generate_random_string(),
    Via = via(Mapped_Address, Branch_ID),

    From_URI = ["<sip:", Userid, $@, ?SIP_SERVER, ">"],
    From_Tag = utils:generate_random_number(9999999999),
    From = from(From_URI, From_Tag),
 
    To_URI = ["<sip:", To_ID, $@, ?SIP_SERVER, ">"],
    To = to(To_URI),
    
    CSeq = cseq(CSeq_Num, Method),

    Call_ID_Str = utils:generate_random_string(),
    Call_ID = callid(Call_ID_Str),

    Max_Forwards = max_forwards(),
    
    Contact = contact(Userid, Mapped_Address),

    User_Agent = user_agent(), 

    Content_Type = content_type("application/sdp"),
    
    Authorization = authorization(Method, Userid, Realm, Nonce, ?SIP_SERVER, "md5", Digest, Client_Nonce,
                                  lists:foldl(fun(X, Y) -> Y ++ [X+48] end, [], binary_to_list(<<Nonce_Count:8/integer-unit:8>>)),
                                  QOP),

    if
        SIP_Session_State#sip_session_state.media_session_pid /= -1 ->
            case catch media_session:start([SIP_Session_PID]) of
                {ok, Media_Session_PID} ->
                    case catch gen_server:call(Media_Session_PID, create_media_socket, infinity) of
                        {ok, RTP_Port} ->
                            SDP_Data = generate_sdp([Userid, RTP_Port]),
                            
                            Content_Length = content_length(length(SDP_Data)),
                            
                            Request = [Request_Line, Via, From, To, CSeq, Call_ID, Max_Forwards, Contact, User_Agent, Content_Type, Authorization, Content_Length, "\r\n", SDP_Data],
                            Transaction_Record = #transaction_record{auth_count = Auth_Count, nonce = Nonce, nonce_count = Nonce_Count, to = To_ID},
                            ets:insert(Transaction_Table, #transaction{branch_id = Branch_ID, method = "INVITE", cseq = CSeq_Num, call_id = Call_ID_Str, transaction_record = Transaction_Record}),
                            {Branch_ID, Request, SIP_Session_State#sip_session_state{cseq = CSeq_Num + 1, media_session_pid = Media_Session_PID, ua_state = ?CALL_IN_PROGRESS}};
                        {error, Error_Code} ->
                            gen_server:cast(Media_Session_PID, kill_process),
                            {error, Error_Code};
                        _ ->
                            gen_server:cast(Media_Session_PID, kill_process),
                            {error, "106"}
                    end;
                _ ->
                    {error, "106"}
            end;
        true ->
            case catch gen_server:call(SIP_Session_State#sip_session_state.media_session_pid, get_port, infinity) of
                {ok, RTP_Port} ->
                    SDP_Data = generate_sdp([Userid, RTP_Port]),
                    
                    Content_Length = content_length(length(SDP_Data)),
                    
                    Request = [Request_Line, Via, From, To, CSeq, Call_ID, Max_Forwards, Contact, User_Agent, Content_Type, Authorization, Content_Length, "\r\n", SDP_Data],
                    Transaction_Record = #transaction_record{auth_count = Auth_Count, nonce = Nonce, nonce_count = Nonce_Count, to = To_ID},
                    ets:insert(Transaction_Table, #transaction{branch_id = Branch_ID, method = "INVITE", cseq = CSeq_Num, call_id = Call_ID_Str, transaction_record = Transaction_Record}),
                    {Branch_ID, Request, SIP_Session_State#sip_session_state{cseq = CSeq_Num + 1, ua_state = ?CALL_IN_PROGRESS}};
                {error, Error_Code} ->
                    gen_server:cast(SIP_Session_State#sip_session_state.media_session_pid, kill_process),
                    {error, Error_Code};
                _ ->
                    gen_server:cast(SIP_Session_State#sip_session_state.media_session_pid, kill_process),
                    {error, "106"}
            end
    end;

packet([ack, Parse_Record, SIP_Session_State]) ->
    #sip_session_state{mapped_address = Mapped_Address, dialog_table = Dialog_Table} = SIP_Session_State,
    #parse_record{branch_id = Branch_ID, cseq = CSeq_Num, from = From_URI, to = To_URI, from_tag = From_Tag, to_tag = To_Tag, call_id = Call_ID_Str} = Parse_Record,

    Dialog_List = ets:match_object(Dialog_Table, #dialog{call_id = Call_ID_Str, _='_'}),
    if
        length(Dialog_List) >= 1 ->
            Dialog_State = hd(Dialog_List);
        true ->
            Dialog_State = ""
    end,

    Method = "ACK",
    
    if 
        Dialog_State == "" ->
            Route = "",
            Request_Line = request_line(Method, convert_to_request_uri(To_URI));
        true ->
            #dialog{remote_target = Remote_Target, route = Route_List} = Dialog_State,
            if
                Route_List /= [] ->
                    {Request_URI, New_Route_List} = get_route(Remote_Target, Route_List),
                    Route = route(New_Route_List),
                    Request_Line = request_line(Method, Request_URI);
                true ->
                    Route = "",
                    Request_Line = request_line(Method, convert_to_request_uri(To_URI))
            end
    end,

    Via = via(Mapped_Address, Branch_ID),
    
    From = from(From_URI, From_Tag),
 
    To = to(To_URI, To_Tag),
    
    CSeq = cseq(CSeq_Num, Method),

    Call_ID = callid(Call_ID_Str),

    Max_Forwards = max_forwards(),

    User_Agent = user_agent(), 

    Content_Length = content_length(0),
    
    [Request_Line, Via, Route, From, To, CSeq, Call_ID, Max_Forwards, User_Agent, Content_Length, "\r\n"];

packet([cancel, To_ID, Transaction_State, SIP_Session_State]) ->
    #sip_session_state{userid = Userid, mapped_address = Mapped_Address, transaction_table = Transaction_Table} = SIP_Session_State,
    #transaction{branch_id = Branch_ID, cseq = CSeq_Num, call_id = Call_ID_Str} = Transaction_State,

    Method = "CANCEL",

    Request_Line = request_line(Method, "sip:" ++ To_ID ++ "@" ++ ?SIP_SERVER),
    
    Via = via(Mapped_Address, Branch_ID),
    
    From_URI = ["<sip:", Userid, $@, ?SIP_SERVER, ">"],
    From_Tag = utils:generate_random_number(9999999999),
    From = from(From_URI, From_Tag),
 
    To_URI = ["<sip:", To_ID, $@, ?SIP_SERVER, ">"],
    To = to(To_URI),
    
    CSeq = cseq(CSeq_Num, Method),

    Call_ID = callid(Call_ID_Str),

    Max_Forwards = max_forwards(),

    User_Agent = user_agent(), 

    Content_Length = content_length(0),

    Transaction_Record = #transaction_record{to = To_ID},
    catch ets:insert(Transaction_Table, #transaction{branch_id = Branch_ID, method = "CANCEL", cseq = CSeq_Num, call_id = Call_ID_Str, transaction_record = Transaction_Record}),
    
    {Branch_ID, [Request_Line, Via, From, To, CSeq, Call_ID, Max_Forwards, User_Agent, Content_Length, "\r\n"]};

packet([bye, Dialog_State, SIP_Session_State]) ->
    #sip_session_state{mapped_address = Mapped_Address, cseq = CSeq_Num, transaction_table = Transaction_Table} = SIP_Session_State,
    #dialog{remote_target = Remote_Target, route = Route_List, call_id = Call_ID_Str, local_uri = From_URI, local_tag = From_Tag, remote_uri = To_URI, remote_tag = To_Tag} = Dialog_State,

    Method = "BYE",

    if
        Route_List /= [] ->
            {Request_URI, New_Route_List} = get_route(Remote_Target, Route_List),
            Route = route(New_Route_List),
            Request_Line = request_line(Method, Request_URI);
        true ->
            Route = "",
            Request_Line = request_line(Method, convert_to_request_uri(To_URI))
    end,

    Branch_ID = "z9hG4bK" ++ utils:generate_random_string(),
    Via = via(Mapped_Address, Branch_ID),
    
    From = from(From_URI, From_Tag),
 
    To = to(To_URI, To_Tag),
    
    CSeq = cseq(CSeq_Num, Method),

    Call_ID = callid(Call_ID_Str),

    Max_Forwards = max_forwards(),

    User_Agent = user_agent(), 

    Content_Length = content_length(0),
    
    Request = [Request_Line, Via, Route, From, To, CSeq, Call_ID, Max_Forwards, User_Agent, Content_Length, "\r\n"],
    
    ets:insert(Transaction_Table, #transaction{branch_id = Branch_ID, method = "BYE", cseq = CSeq_Num, call_id = Call_ID_Str}),
    {Branch_ID, Request, SIP_Session_State#sip_session_state{cseq = CSeq_Num + 1}}.
    
request_line(Method, URI) ->
    [Method, $\s, URI, $\s, "SIP/2.0\r\n"].

via(Mapped_Address, Branch_ID) ->
    {IP, Port} = Mapped_Address,
    ["Via:", $\s, "SIP/2.0/UDP", $\s, IP, $:, integer_to_list(Port), $;, "rport", $;, "branch=", Branch_ID, "\r\n"].

route(Route_List) ->
    lists:foldl(
      fun(X, Y) ->
              ["Route:", $\s, X, "\r\n"] ++ Y
      end, [], Route_List).

from(From_URI, From_Tag) when is_integer(From_Tag) ->
    ["From:", $\s, From_URI, $;, "tag=", integer_to_list(From_Tag), "\r\n"];
from(From_URI, From_Tag) ->
    ["From:", $\s, From_URI, $;, "tag=", From_Tag, "\r\n"].

to(To_URI) ->
    ["To:", $\s, To_URI, "\r\n"].

to(To_URI, To_Tag) when is_integer(To_Tag) ->
    ["To:", $\s, To_URI, $;, "tag=", integer_to_list(To_Tag), "\r\n"];
to(To_URI, To_Tag) ->
    ["To:", $\s, To_URI, $;, "tag=", To_Tag, "\r\n"].

cseq(CSeq_Num, Method) when is_list(CSeq_Num) ->
    ["CSeq:", $\s, CSeq_Num, $\s, Method, "\r\n"];

cseq(CSeq_Num, Method) ->
    ["CSeq:", $\s, integer_to_list(CSeq_Num), $\s, Method, "\r\n"].

callid(Call_ID_Str) ->
    ["Call-ID:", $\s, Call_ID_Str, "\r\n"].

max_forwards() ->
    ["Max-Forwards:", $\s, "70", "\r\n"].

contact(Userid, Mapped_Address) ->
    {IP, Port} = Mapped_Address,
    ["Contact:", $\s, "<sip:", Userid, $\@, IP, $:, integer_to_list(Port), $>, "\r\n"].

user_agent() ->
    ["User-Agent:", $\s, ?USER_AGENT, "\r\n"].

expires(Expires) ->
    ["Expires:", $\s, integer_to_list(Expires), "\r\n"].

content_length(Length) ->
    ["Content-Length:", $\s, integer_to_list(Length), "\r\n"].

content_type(Type) ->
    ["Content-Type:", $\s, Type, "\r\n"].

authorization(Method, Userid, Realm, Nonce, Domain_Name, Algorithm, Digest, Client_Nonce, Nonce_Count, QOP) ->
    if
        QOP == undefined ->
            Authorization = ["Digest ", "username=\"", Userid, "\",", "realm=\"", Realm, "\",", "nonce=\"", Nonce, "\",", "uri=\"", "sip:", Domain_Name, "\",", "algorithm=", Algorithm, $,, "response=\"", Digest, "\"", "\r\n"];
        true ->
            Authorization = ["Digest ", "username=\"", Userid, "\",", "realm=\"", Realm, "\",", "nonce=\"", Nonce, "\",", "uri=\"", "sip:", Domain_Name, "\",", "algorithm=", Algorithm, $,, "response=\"", Digest, "\",", "cnonce=\"", Client_Nonce, "\",", "nc=\"", Nonce_Count, "\",", "qop=\"", QOP, "\"", "\r\n"]
    end,
    if
        Method == "REGISTER" ->
            ["Authorization: ", Authorization];
        true ->
            ["Proxy-Authorization: ", Authorization]
    end.

generate_sdp([Userid, RTP_Port]) ->
    case catch inet:getif() of
        {ok, Network_List} ->
            IP_List = 
                [ inet_parse:ntoa(element(1, X)) || X <- Network_List, 
                       case catch inet_parse:ntoa(element(1, Network_List)) of
                           "127.0.0.1" -> false;
                           _ -> true
                       end ],
            if
                length(IP_List) > 0 -> IP = hd(IP_List);
                true -> IP = ""
            end;
        _ ->
            IP = ""
    end,
    Version = version(),
    Session_ID = utils:generate_random_number(16#FFFFF),
    Session_Ver = utils:generate_random_number(16#FFFFF),
    Origin = origin([Userid, IP, Session_ID, Session_Ver]),
    Session_Name = session_name(),
    Connection_Info = connection_info(IP),
    Time = time_info(),
    Media = media(RTP_Port),
    Media_Attributes = 
        lists:foldl(
          fun(X, Y) ->
                  case X of
                      111 ->
                          Y ++ media_attr([rtpmap, "111", "Speex", "16000", "1"]);
                      110 ->
                          Y ++ media_attr([rtpmap, "110", "Speex", "8000", "1"]);
                      _ ->
                          Y
                  end
          end, [], ?MEDIA_LIST),
    lists:flatten([Version, Origin, Session_Name, Connection_Info, Time, Media, Media_Attributes]).
    
version() ->
    ["v=0", "\r\n"].

origin([Username, IP, Session_ID, Session_Ver]) ->
    ["o=", Username, $\s, integer_to_list(Session_ID), $\s, integer_to_list(Session_Ver), $\s, "IN", $\s, "IP4", $\s, IP, "\r\n"].

session_name() ->
    ["s=P2P Voice Chat", "\r\n"].

connection_info(IP) ->
    ["c=IN IP4", $\s, IP, "\r\n"].

time_info() ->
    ["t=0 0", "\r\n"].

media(RTP_Port) ->
    Media_List = 
        lists:foldl(
          fun(X, Y) ->
                  if 
                      Y == [] ->
                          integer_to_list(X);
                      true ->
                          Y ++ " " ++ integer_to_list(X)
                  end
          end, [], ?MEDIA_LIST),
    ["m=audio", $\s, integer_to_list(RTP_Port), $\s, "RTP/AVP", $\s, Media_List, "\r\n"].

media_attr([rtpmap, Payload_Type, Encoding_Name, Clock_Rate, Channel_Num]) ->
    ["a=", "rtpmap:", Payload_Type, $\s, Encoding_Name, $/, Clock_Rate, $/, Channel_Num, "\r\n"].

get_route(Remote_Target, [H|T]) ->
    case re:run(H, "lr;", []) of
        {match, _} ->
            {convert_to_request_uri(Remote_Target), [H|T]};
        _ ->
            {convert_to_request_uri(H), T ++ [Remote_Target]}
    end.
    
convert_to_request_uri(URI) ->
    Split_List = re:split(URI, ";", [{return, list}]),
    T1 = 
        [ X || X <- Split_List,
               case re:run(X, "sip:", []) of
                   {match, _} ->
                       true;
                   _ ->
                       false
               end ],
    if
        length(T1) >= 1 ->
            T2 = re:replace(hd(T1), "<", "",  [{return, list}]),
            re:replace(T2, ">", "", [{return, list}]);
        true ->
            ""
    end.
